library(dplyr)
library(nycflights13)

数据

library(nycflights13)
dim(flights)
[1] 336776     19
data(flights)

dplyr动作

  1. 取行:filter()
  2. 排序: arrange()
  3. 取列: select()
  4. 生成新变量: mutate()
  5. 分组汇总: group_by() %>% summarise()
  6. 动作连贯:%>%: 快捷键: ctrl + shift + M

filter()

dplyr::filter(flights, month ==1, day == 1)

base R 方法

flights[flights$month == 1 & flights$day == 1, ]

arrange()

arrange(flights, year, month, day)
arrange(flights, desc(arr_delay))

取列

select(flights, year, month, day)
select(flights, year:day)
select(flights, -(year:day))
flights %>% select(contains('dep'))

重命名

select(flights, tail_num = tailnum)
rename(flights, tail_num = tailnum)

增添新变量

mutate(flights,
  gain = arr_delay - dep_delay,
  speed = distance / air_time * 60
)
mutate(flights,
  gain = arr_delay - dep_delay,
  gain_per_hour = gain / (air_time / 60)
)
transmute(flights,
  gain = arr_delay - dep_delay,
  gain_per_hour = gain / (air_time / 60)
)

汇总

summarise(flights,
  delay = mean(dep_delay, na.rm = TRUE)
)

抽样

sample_n(flights, 10)
sample_frac(flights, 0.01)

分组

by_tailnum <- group_by(flights, tailnum)
delay <- summarise(by_tailnum,
  count = n(),
  dist = mean(distance, na.rm = TRUE),
  delay = mean(arr_delay, na.rm = TRUE))
delay <- dplyr::filter(delay, count > 20, dist < 2000)

使用连贯动作

detach(dplyr)
Error in detach(dplyr) : 'name'参数不对
flights %>%
  group_by(year, month, day) %>%
  select(arr_delay, dep_delay) %>%
  summarise(
    arr = mean(arr_delay, na.rm = TRUE),
    dep = mean(dep_delay, na.rm = TRUE)
  ) %>%
  filter(arr > 30 | dep > 30)

多变量汇总

daily <- group_by(flights, year, month, day)
(per_day   <- summarise(daily, flights = n()))
(per_month <- summarise(per_day, flights = sum(flights)))
(per_year  <- summarise(per_month, flights = sum(flights)))

多表合并

flights2 <- flights %>% select(year:day, hour, origin, dest, tailnum, carrier)

flights2 %>% 
  left_join(airlines)
Joining, by = "carrier"
data("weather")
weather
flights2 %>% left_join(weather)
Joining, by = c("year", "month", "day", "hour", "origin")

用特定变量合并交集

planes
flights2 %>% left_join(planes, by = "tailnum")

合并变量名称不同?

airports
flights2 %>% left_join(airports, c("dest" = "faa"))

四种合并(join)方式

df1 <- tibble(x = c(1, 2), y = 2:1)
df2 <- tibble(x = c(1, 3), a = 10, b = "a")
df1
df2
  1. inner_join(x, y): xy交集
df1 %>% inner_join(df2)
Joining, by = "x"
  1. left_join(x, y), 保留x的所有观测。最为常见
df1 %>% left_join(df2)
Joining, by = "x"
  1. right_join(),保留y中的所有观测,等同于left_join(y, x),但列的排序稍有不同。
df1 %>% right_join(df2)
Joining, by = "x"
df2 %>% left_join(df1)
Joining, by = "x"
  1. full_join() 保留x, y所有观测,用NA填充。
df1 %>% full_join(df2)

与观测有关的join

  1. semi_join(x, y): 保留y中有匹配的x观测值
  2. anti_join(x, y):将x中与y有匹配的数据全部丢弃
flights %>% 
  anti_join(planes, by = "tailnum") %>% 
  count(tailnum, sort = TRUE)
df1 <- tibble(x = c(1, 1, 3, 4), y = 1:4)
df2 <- tibble(x = c(1, 1, 2), z = c("a", "b", "a"))
df1
df2
df1 %>% nrow()
[1] 4
df1 %>% inner_join(df2, by = "x")
df1 %>% semi_join(df2, by = "x")

集合操作

(df1 <- tibble(x = 1:2, y = c(1L, 1L)))
(df2 <- tibble(x = 1:2, y = 1:2))
intersect(df1, df2)
union(df1, df2)
setdiff(df1, df2)
setdiff(df2, df1)

窗口函数 (window functions)

与mutate和filter结合使用,窗口函数可以大有作为

library(Lahman)
batting <- Lahman::Batting %>%
  as_tibble() %>%
  select(playerID, yearID, teamID, G, AB:H) %>%
  arrange(playerID, yearID, teamID) %>%
  semi_join(Lahman::AwardsPlayers, by = "playerID")

players <- batting %>% group_by(playerID)
players
mutate(players, G_rank = min_rank(G))
# For each player, find the two years with most hits
filter(players, min_rank(desc(H)) <= 2 & H > 0)
# Within each player, rank each year by the number of games played
mutate(players, G_rank = min_rank(G))
# For each player, find every year that was better than the previous year
filter(players, G > lag(G))
# For each player, compute avg change in games played per year
mutate(players, G_change = (G - lag(G)) / (yearID - lag(yearID)))

# For each player, find all where they played more games than average
filter(players, G > mean(G))
# For each, player compute a z score based on number of games played
mutate(players, G_z = (G - mean(G)) / sd(G))
  1. 排序和计算顺序:row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), and ntile().
  2. 滞后和领先操作: lead() and lag()

排序

x <- c(1, 1, 2, 2, 2)

row_number(x)
[1] 1 2 3 4 5
min_rank(x)
[1] 1 1 3 3 3
dense_rank(x)
[1] 1 1 2 2 2
cume_dist(x)
[1] 0.4 0.4 1.0 1.0 1.0
#> [1] 0.4 0.4 1.0 1.0 1.0
percent_rank(x)
[1] 0.0 0.0 0.5 0.5 0.5
# select the top 10% of records within each group
filter(players, cume_dist(desc(G)) < 0.1)
by_team_player <- group_by(batting, teamID, playerID)
by_team <- summarise(by_team_player, G = sum(G))
by_team_quartile <- group_by(by_team, quartile = ntile(G, 4))
summarise(by_team_quartile, mean(G))

领先滞后

x <- 1:5
lead(x)
lag(x)
# Compute the relative change in games played
mutate(players, G_delta = G - lag(G))
# Find when a player changed teams
filter(players, teamID != lag(teamID))
LS0tDQp0aXRsZTogImRwbHly5LuL57uNIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7cn0NCmRldGFjaCgicGFja2FnZTpkcGx5ciIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpgYGANCg0KIyDmlbDmja4NCmBgYHtyfQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpkaW0oZmxpZ2h0cykNCmRhdGEoZmxpZ2h0cykNCmBgYA0KDQotIOaJgOeUn+aIkOeahOaVsOaNruagvOW8j+aYr2B0aWJibGVg44CC5LuW5pivZGF0YS5mcmFtZeeahOeOsOS7o+eJiOacrO+8jOWFt+acieaVsOaNruahhueahOWkp+WkmuaVsOWxnuaAp++8jOS9huaYr+eJueWIq+mAgueUqOS6juWkp+Wei+aVsOaNrumbhueahOWtmOWCqOWSjOihqOekuuOAgg0KDQotIGh0dHA6Ly90aWJibGUudGlkeXZlcnNlLm9yZw0KDQojIGRwbHly5Yqo5L2cDQoxLiDlj5booYzvvJpgZmlsdGVyKClgDQoyLiDmjpLluo86IGBhcnJhbmdlKClgDQozLiDlj5bliJc6IGBzZWxlY3QoKWANCjQuIOeUn+aIkOaWsOWPmOmHjzogYG11dGF0ZSgpYA0KNS4g5YiG57uE5rGH5oC7OiBgZ3JvdXBfYnkoKSAlPiUgc3VtbWFyaXNlKClgDQo2LiDliqjkvZzov57otK/vvJpgICU+JSBgOiDlv6vmjbfplK7vvJogY3RybCArIHNoaWZ0ICsgTQ0KDQojIGZpbHRlcigpDQpgYGB7cn0NCmRwbHlyOjpmaWx0ZXIoZmxpZ2h0cywgbW9udGggPT0xLCBkYXkgPT0gMSkNCmBgYA0KDQpiYXNlIFIg5pa55rOVDQpgYGB7cn0NCmZsaWdodHNbZmxpZ2h0cyRtb250aCA9PSAxICYgZmxpZ2h0cyRkYXkgPT0gMSwgXQ0KYGBgDQoNCiMgYXJyYW5nZSgpDQpgYGB7cn0NCmFycmFuZ2UoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkNCmBgYA0KDQotIOWAkuW6j++8mmRlc2NlbmRpbmcgb3JkZXINCmBgYHtyfQ0KYXJyYW5nZShmbGlnaHRzLCBkZXNjKGFycl9kZWxheSkpDQpgYGANCg0KIyDlj5bliJcNCmBgYHtyfQ0Kc2VsZWN0KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpDQpgYGANCg0KYGBge3J9DQpzZWxlY3QoZmxpZ2h0cywgeWVhcjpkYXkpDQpgYGANCg0KYGBge3J9DQpzZWxlY3QoZmxpZ2h0cywgLSh5ZWFyOmRheSkpDQpgYGANCg0KLSBzZWxlY3Tlj6/ku6XphY3lkIhgc3RhcnRzX3dpdGgoKWAgYGVuZHNfd2l0aCgpYCwgYG1hdGNoZXMoKWAsIGBjb250YWlucygpYOS9v+eUqOOAgg0KYGBge3J9DQpmbGlnaHRzICU+JSBzZWxlY3QoY29udGFpbnMoJ2RlcCcpKQ0KYGBgDQoNCiMg6YeN5ZG95ZCNDQpgYGB7cn0NCnNlbGVjdChmbGlnaHRzLCB0YWlsX251bSA9IHRhaWxudW0pDQpgYGANCg0KYGBge3J9DQpyZW5hbWUoZmxpZ2h0cywgdGFpbF9udW0gPSB0YWlsbnVtKQ0KYGBgDQoNCg0KIyDlop7mt7vmlrDlj5jph48NCmBgYHtyfQ0KbXV0YXRlKGZsaWdodHMsDQogIGdhaW4gPSBhcnJfZGVsYXkgLSBkZXBfZGVsYXksDQogIHNwZWVkID0gZGlzdGFuY2UgLyBhaXJfdGltZSAqIDYwDQopDQpgYGANCg0KLSDlrp7ml7blj5jph4/nlJ/miJANCg0KYGBge3J9DQptdXRhdGUoZmxpZ2h0cywNCiAgZ2FpbiA9IGFycl9kZWxheSAtIGRlcF9kZWxheSwNCiAgZ2Fpbl9wZXJfaG91ciA9IGdhaW4gLyAoYWlyX3RpbWUgLyA2MCkNCikNCmBgYA0KDQotIOWPquS/neeVmeaWsOWPmOmHjw0KYGBge3J9DQp0cmFuc211dGUoZmxpZ2h0cywNCiAgZ2FpbiA9IGFycl9kZWxheSAtIGRlcF9kZWxheSwNCiAgZ2Fpbl9wZXJfaG91ciA9IGdhaW4gLyAoYWlyX3RpbWUgLyA2MCkNCikNCmBgYA0KDQoNCiMg5rGH5oC7DQpgYGB7cn0NCnN1bW1hcmlzZShmbGlnaHRzLA0KICBkZWxheSA9IG1lYW4oZGVwX2RlbGF5LCBuYS5ybSA9IFRSVUUpDQopDQpgYGANCg0KDQotIOWlveWDj+W+iOWkmuS9me+8nw0KDQotIGdyb3VwX2J5KCkg5Li6IHN1bW1hcmlzZeaPkuS4iue/heiGgOOAgueojeWQjuWNleeLrOWxleekuuOAgg0KDQoNCiMg5oq95qC3DQoNCmBgYHtyfQ0Kc2FtcGxlX24oZmxpZ2h0cywgMTApDQpgYGANCg0KDQotIOaMieavlOS+i+aKveagtw0KYGBge3J9DQpzYW1wbGVfZnJhYyhmbGlnaHRzLCAwLjAxKQ0KYGBgDQoNCiMg5YiG57uEDQoNCmBgYHtyfQ0KYnlfdGFpbG51bSA8LSBncm91cF9ieShmbGlnaHRzLCB0YWlsbnVtKQ0KZGVsYXkgPC0gc3VtbWFyaXNlKGJ5X3RhaWxudW0sDQogIGNvdW50ID0gbigpLA0KICBkaXN0ID0gbWVhbihkaXN0YW5jZSwgbmEucm0gPSBUUlVFKSwNCiAgZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKSkNCmRlbGF5IDwtIGRwbHlyOjpmaWx0ZXIoZGVsYXksIGNvdW50ID4gMjAsIGRpc3QgPCAyMDAwKQ0KYGBgDQoNCuS9v+eUqOi/nui0r+WKqOS9nA0KYGBge3J9DQpkZWxheSA8LSBmbGlnaHRzICU+JSANCiAgICBncm91cF9ieSh0YWlsbnVtKSAlPiUgDQogICAgc3VtbWFyaXNlKGNvdW50ID0gbigpLA0KICAgICAgICAgICAgICBkaXN0ID0gbWVhbihkaXN0YW5jZSwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgICAgZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKSkgJT4lIA0KICAgIGZpbHRlcihjb3VudCA+IDIwLCBkaXN0IDwgMjAwMCkNCmRlbGF5DQpgYGANCg0KLSDmoLnmja7ov57otK/mk43kvZzlvojlrrnmmJPnnIvmh4Lku6PnoIENCg0KYGBge3J9DQpmbGlnaHRzICU+JQ0KICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUNCiAgc2VsZWN0KGFycl9kZWxheSwgZGVwX2RlbGF5KSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGFyciA9IG1lYW4oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpLA0KICAgIGRlcCA9IG1lYW4oZGVwX2RlbGF5LCBuYS5ybSA9IFRSVUUpDQogICkgJT4lDQogIGZpbHRlcihhcnIgPiAzMCB8IGRlcCA+IDMwKQ0KYGBgDQoNCg0KIyDlpJrlj5jph4/msYfmgLsNCmBgYHtyfQ0KZGFpbHkgPC0gZ3JvdXBfYnkoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkNCihwZXJfZGF5ICAgPC0gc3VtbWFyaXNlKGRhaWx5LCBmbGlnaHRzID0gbigpKSkNCihwZXJfbW9udGggPC0gc3VtbWFyaXNlKHBlcl9kYXksIGZsaWdodHMgPSBzdW0oZmxpZ2h0cykpKQ0KKHBlcl95ZWFyICA8LSBzdW1tYXJpc2UocGVyX21vbnRoLCBmbGlnaHRzID0gc3VtKGZsaWdodHMpKSkNCmBgYA0KDQojIOWkmuihqOWQiOW5tg0KYGBge3J9DQpmbGlnaHRzMiA8LSBmbGlnaHRzICU+JSBzZWxlY3QoeWVhcjpkYXksIGhvdXIsIG9yaWdpbiwgZGVzdCwgdGFpbG51bSwgY2FycmllcikNCg0KZmxpZ2h0czIgJT4lIA0KICBsZWZ0X2pvaW4oYWlybGluZXMpDQpgYGANCmBgYHtyfQ0KZGF0YSgid2VhdGhlciIpDQp3ZWF0aGVyDQpmbGlnaHRzMiAlPiUgbGVmdF9qb2luKHdlYXRoZXIpDQpgYGANCg0KDQojIOeUqOeJueWumuWPmOmHj+WQiOW5tuS6pOmbhg0KYGBge3J9DQpwbGFuZXMNCmZsaWdodHMyICU+JSBsZWZ0X2pvaW4ocGxhbmVzLCBieSA9ICJ0YWlsbnVtIikNCmBgYA0KDQojIOWQiOW5tuWPmOmHj+WQjeensOS4jeWQjO+8nw0KYGBge3J9DQphaXJwb3J0cw0KZmxpZ2h0czIgJT4lIGxlZnRfam9pbihhaXJwb3J0cywgYygiZGVzdCIgPSAiZmFhIikpDQpmbGlnaHRzMiAlPiUgbGVmdF9qb2luKGFpcnBvcnRzLCBjKCJvcmlnaW4iID0gImZhYSIpKQ0KYGBgDQoNCiMg5Zub56eN5ZCI5bm277yIam9pbu+8ieaWueW8jw0KYGBge3J9DQpkZjEgPC0gdGliYmxlKHggPSBjKDEsIDIpLCB5ID0gMjoxKQ0KZGYyIDwtIHRpYmJsZSh4ID0gYygxLCAzKSwgYSA9IDEwLCBiID0gImEiKQ0KZGYxDQpkZjINCmBgYA0KMS4gYGlubmVyX2pvaW4oeCwgeSlgOiB4eeS6pOmbhg0KDQpgYGB7cn0NCmRmMSAlPiUgaW5uZXJfam9pbihkZjIpDQpgYGANCjIuIGBsZWZ0X2pvaW4oeCwgeSlgLCDkv53nlZl455qE5omA5pyJ6KeC5rWL44CC5pyA5Li65bi46KeBDQpgYGB7cn0NCmRmMSAlPiUgbGVmdF9qb2luKGRmMikNCmBgYA0KDQozLiBgcmlnaHRfam9pbigpYO+8jOS/neeVmXnkuK3nmoTmiYDmnInop4LmtYvvvIznrYnlkIzkuo5gbGVmdF9qb2luKHksIHgpYCzkvYbliJfnmoTmjpLluo/nqI3mnInkuI3lkIzjgIINCmBgYHtyfQ0KZGYxICU+JSByaWdodF9qb2luKGRmMikNCmRmMiAlPiUgbGVmdF9qb2luKGRmMSkNCmBgYA0KNC4gYGZ1bGxfam9pbigpYCDkv53nlZl4LCB55omA5pyJ6KeC5rWL77yM55SoTkHloavlhYXjgIINCmBgYHtyfQ0KZGYxICU+JSBmdWxsX2pvaW4oZGYyKQ0KYGBgDQoNCiMg5LiO6KeC5rWL5pyJ5YWz55qEam9pbg0KMS4gYHNlbWlfam9pbih4LCB5KWA6IOS/neeVmXnkuK3mnInljLnphY3nmoR46KeC5rWL5YC8DQoyLiBgYW50aV9qb2luKHgsIHkpYO+8muWwhnjkuK3kuI555pyJ5Yy56YWN55qE5pWw5o2u5YWo6YOo5Lii5byDDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBhbnRpX2pvaW4ocGxhbmVzLCBieSA9ICJ0YWlsbnVtIikgJT4lIA0KICBjb3VudCh0YWlsbnVtLCBzb3J0ID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCmRmMSA8LSB0aWJibGUoeCA9IGMoMSwgMSwgMywgNCksIHkgPSAxOjQpDQpkZjIgPC0gdGliYmxlKHggPSBjKDEsIDEsIDIpLCB6ID0gYygiYSIsICJiIiwgImEiKSkNCmRmMQ0KZGYyDQpkZjEgJT4lIG5yb3coKQ0KZGYxICU+JSBpbm5lcl9qb2luKGRmMiwgYnkgPSAieCIpDQpkZjEgJT4lIHNlbWlfam9pbihkZjIsIGJ5ID0gIngiKQ0KYGBgDQoNCiMg6ZuG5ZCI5pON5L2cDQotIGBpbnRlcnNlY3QoeCwgeSlgOiB4eeS6pOmbhg0KLSBgdW5pb24oeCwgeSlgOiB4eeW5tumbhg0KLSBgc2V0ZGlmZih4LCB5KWA6IHjkuK1555qE6KGl6ZuGDQpgYGB7cn0NCihkZjEgPC0gdGliYmxlKHggPSAxOjIsIHkgPSBjKDFMLCAxTCkpKQ0KKGRmMiA8LSB0aWJibGUoeCA9IDE6MiwgeSA9IDE6MikpDQppbnRlcnNlY3QoZGYxLCBkZjIpDQp1bmlvbihkZjEsIGRmMikNCnNldGRpZmYoZGYxLCBkZjIpDQpzZXRkaWZmKGRmMiwgZGYxKQ0KYGBgDQoNCiMg56qX5Y+j5Ye95pWwICh3aW5kb3cgZnVuY3Rpb25zKQ0KDQrkuI5tdXRhdGXlkoxmaWx0ZXLnu5PlkIjkvb/nlKjvvIznqpflj6Plh73mlbDlj6/ku6XlpKfmnInkvZzkuLoNCg0KYGBge3J9DQpsaWJyYXJ5KExhaG1hbikNCmJhdHRpbmcgPC0gTGFobWFuOjpCYXR0aW5nICU+JQ0KICBhc190aWJibGUoKSAlPiUNCiAgc2VsZWN0KHBsYXllcklELCB5ZWFySUQsIHRlYW1JRCwgRywgQUI6SCkgJT4lDQogIGFycmFuZ2UocGxheWVySUQsIHllYXJJRCwgdGVhbUlEKSAlPiUNCiAgc2VtaV9qb2luKExhaG1hbjo6QXdhcmRzUGxheWVycywgYnkgPSAicGxheWVySUQiKQ0KDQpwbGF5ZXJzIDwtIGJhdHRpbmcgJT4lIGdyb3VwX2J5KHBsYXllcklEKQ0KcGxheWVycw0KYGBgDQoNCg0KDQpgYGB7cn0NCiMgRm9yIGVhY2ggcGxheWVyLCBmaW5kIHRoZSB0d28geWVhcnMgd2l0aCBtb3N0IGhpdHMNCmZpbHRlcihwbGF5ZXJzLCBtaW5fcmFuayhkZXNjKEgpKSA8PSAyICYgSCA+IDApDQojIFdpdGhpbiBlYWNoIHBsYXllciwgcmFuayBlYWNoIHllYXIgYnkgdGhlIG51bWJlciBvZiBnYW1lcyBwbGF5ZWQNCm11dGF0ZShwbGF5ZXJzLCBHX3JhbmsgPSBtaW5fcmFuayhHKSkNCiMgRm9yIGVhY2ggcGxheWVyLCBmaW5kIGV2ZXJ5IHllYXIgdGhhdCB3YXMgYmV0dGVyIHRoYW4gdGhlIHByZXZpb3VzIHllYXINCmZpbHRlcihwbGF5ZXJzLCBHID4gbGFnKEcpKQ0KIyBGb3IgZWFjaCBwbGF5ZXIsIGNvbXB1dGUgYXZnIGNoYW5nZSBpbiBnYW1lcyBwbGF5ZWQgcGVyIHllYXINCm11dGF0ZShwbGF5ZXJzLCBHX2NoYW5nZSA9IChHIC0gbGFnKEcpKSAvICh5ZWFySUQgLSBsYWcoeWVhcklEKSkpDQoNCiMgRm9yIGVhY2ggcGxheWVyLCBmaW5kIGFsbCB3aGVyZSB0aGV5IHBsYXllZCBtb3JlIGdhbWVzIHRoYW4gYXZlcmFnZQ0KZmlsdGVyKHBsYXllcnMsIEcgPiBtZWFuKEcpKQ0KIyBGb3IgZWFjaCwgcGxheWVyIGNvbXB1dGUgYSB6IHNjb3JlIGJhc2VkIG9uIG51bWJlciBvZiBnYW1lcyBwbGF5ZWQNCm11dGF0ZShwbGF5ZXJzLCBHX3ogPSAoRyAtIG1lYW4oRykpIC8gc2QoRykpDQpgYGANCg0KLSDnqpflj6Plh73mlbDlj6/ku6XorqnmiJHku6zlnKjooYzlkozliJfmk43kvZzml7bmm7TliqDoh6rlpoLvvIzlroPku6zkuLvopoHliIbkuLo15Lit77yM5YWI5LuL57uN5Lik56eNDQoxLiDmjpLluo/lkozorqHnrpfpobrluo/vvJpyb3dfbnVtYmVyKCksIG1pbl9yYW5rKCksIGRlbnNlX3JhbmsoKSwgY3VtZV9kaXN0KCksIHBlcmNlbnRfcmFuaygpLCBhbmQgbnRpbGUoKS4gDQoyLiDmu57lkI7lkozpooblhYjmk43kvZzvvJogbGVhZCgpIGFuZCBsYWcoKQ0KDQoNCg0KIyDmjpLluo8NCmBgYHtyfQ0KeCA8LSBjKDEsIDEsIDIsIDIsIDIpDQoNCnJvd19udW1iZXIoeCkNCg0KbWluX3JhbmsoeCkNCg0KZGVuc2VfcmFuayh4KQ0KDQpjdW1lX2Rpc3QoeCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBzZWxlY3QgdGhlIHRvcCAxMCUgb2YgcmVjb3JkcyB3aXRoaW4gZWFjaCBncm91cA0KZmlsdGVyKHBsYXllcnMsIGN1bWVfZGlzdChkZXNjKEcpKSA8IDAuMSkNCmBgYA0KDQotIG50aWxlKCkg5bCG5Y+Y6YeP5pWw5o2u5YiG5Li655u4562J55qEbuWdl+OAgg0KYGBge3J9DQpieV90ZWFtX3BsYXllciA8LSBncm91cF9ieShiYXR0aW5nLCB0ZWFtSUQsIHBsYXllcklEKQ0KYnlfdGVhbSA8LSBzdW1tYXJpc2UoYnlfdGVhbV9wbGF5ZXIsIEcgPSBzdW0oRykpDQpieV90ZWFtX3F1YXJ0aWxlIDwtIGdyb3VwX2J5KGJ5X3RlYW0sIHF1YXJ0aWxlID0gbnRpbGUoRywgNCkpDQpzdW1tYXJpc2UoYnlfdGVhbV9xdWFydGlsZSwgbWVhbihHKSkNCmBgYA0KDQoNCiMg6aKG5YWI5rue5ZCODQpgYGB7cn0NCnggPC0gMTo1DQpsZWFkKHgpDQpsYWcoeCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBDb21wdXRlIHRoZSByZWxhdGl2ZSBjaGFuZ2UgaW4gZ2FtZXMgcGxheWVkDQptdXRhdGUocGxheWVycywgR19kZWx0YSA9IEcgLSBsYWcoRykpDQojIEZpbmQgd2hlbiBhIHBsYXllciBjaGFuZ2VkIHRlYW1zDQpmaWx0ZXIocGxheWVycywgdGVhbUlEICE9IGxhZyh0ZWFtSUQpKQ0KYGBgDQoNCg0KLSDoh6rlrablkI7kuInnp43liqjkvZzvvIznjrDlrp7kuK3nlKjnmoTkuI3lpKrlpJrvvIzkuobop6Pmu57lkI7vvIzlj6/ku6XpnIDopoHnmoTml7blgJnlho3mn6XpmIXluK7liqnmlofmoaPjgILkvYbku6XkuIrmk43kvZzlupTor6Xng4Lnhp/kuo7og7jjgIINCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=